﻿/**
 * ZContainer
 *
 * @author		Allen Chou
 * @version		1.0.2 (last update: Jan 25 2009)
 * @link        http://cjcat.blogspot.com
 * @link		http://cjcat2266.deviantart.com
 */

package idv.cjcat.display.zcontainer {
	import flash.display.*;
	import flash.errors.IllegalOperationError;
	import flash.events.Event;
	
	import idv.cjcat.display.*;
	
	/**
	 * The <code>ZContainer</code> class is a 2.5D engine,
	 * which does not make use of the build-in 3D engine in Flash Player 10.
	 * 
	 * <p>
	 * The <code>ZContainer</code> class supports both orthogonal projection and perspective projection.
	 * </p>
	 */
	public class ZContainer extends NonAddChildContainer {
		
		/**
		 * The focal length of the camera.
		 * <p>This property is not used in orthogonal projection.</p>
		 */
		public var focalLength:Number;
		
		/**
		 * The x coordinate of the camera.
		 */
		public var cameraX:Number = 0;
		/**
		 * The y coordinate of the camera.
		 */
		public var cameraY:Number = 0;
		/**
		 * The z coordinate of the camera.
		 */
		public var cameraZ:Number = -1000;
		
		private var zsVec:Vector.<ZSprite> = new Vector.<ZSprite>();
		
		/**
		 * Creates a 2.5D container.
		 * @param	usePerspective  True for perspective projection; false for orthogonal projection.
		 * @param	focalLength     The focal length of the camera. The focal length of the camera. This property is not used in orthogonal projection.
		 */
		public function ZContainer(usePerspective:Boolean = false, focalLength:Number = 1000) {
			this.usePerspective = usePerspective;
			this.focalLength = focalLength;
		}
		
		/**
		 * Use this method to add <code>ZSprite</code> objects to the <code>ZContainer's</code> display list.
		 * @param	child  The <code>ZSprite</code> object. Adding <code>DisplayObjects</code> of other types will induce an error.
		 * @return  The <code>ZSprite</code> object.
		 */
		public override function addChild(child:DisplayObject):DisplayObject {
			if (!(child is ZSprite)) throw new IllegalOperationError("The child added must be a ZSprite object.");
			if (zsVec.indexOf(child as ZSprite) < 0) zsVec.push(child as ZSprite);
			return _sprite.addChild(child);
		}
		
		/**
		 * Use this method to remove <code>ZSprite</code> objects from the <code>ZContainer's</code> display list.
		 * @param	child  The <code>ZSprite</code> object. Removing <code>DisplayObjects</code> of other types will induce an error.
		 * @return  The <code>ZSprite</code> object.
		 */
		public override function removeChild(child:DisplayObject):DisplayObject {
			if (!(child is ZSprite)) throw new IllegalOperationError("The child added must be a ZSprite object.");
			if (zsVec.indexOf(child as ZSprite) >= 0) zsVec.splice(zsVec.indexOf(child as ZSprite), 1);
			return _sprite.removeChild(child);
		}
		
		/**
		 * Removes all <code>ZSprites</code> from the <code>ZContainer</code>.
		 */
		public function clear():void {
			while (_sprite.numChildren > 0) _sprite.removeChildAt(0);
			zsVec.length = 0;
		}
		
		/**
		 * Whether the <code>ZContainer</code> uses perspective projection or orthogonal projection.
		 */
		public function get usePerspective():Boolean {
			if (render == renderPerspective) return true;
			return false;
		}
		public function set usePerspective(value:Boolean):void {
			if (value) render = renderPerspective;
			else render = renderOrthographic;
		}
		
		/**
		 * Repeatedly call this method to render the <code>ZContainer</code>.
		 * <p>This method can be used as an event listener.</p>
		 */
		public var render:Function;
		private var dz:Number, d:Number, zs:ZSprite;
		private function renderPerspective(e:Event = null):void {
			//z-sorting
			zsVec.sort(zSorter);
			
			//perspective projection
			for each (zs in zsVec) {
				_sprite.addChild(zs);
				
				dz = zs.z - cameraZ;
				if (dz < 0) {
					zs._outOfRange = true;
					zs._visibleBeforeOutOfRange = zs.visible;
					zs.visible = false;
					continue;
				} else {
					if (zs._outOfRange) {
						zs._outOfRange = false;
						zs.visible = zs._visibleBeforeOutOfRange;
					}
				}
				
				d = dz / focalLength;
				zs.superX = (zs.x - cameraX) / (d + 1);
				zs.superY = (zs.y - cameraY) / (d + 1);
				
				zs.scaleX = zs.scaleY = 1 / (d + 1);
			}
		}
		private function renderOrthographic(e:Event = null):void {
			//z-sorting
			zsVec.sort(zSorter);
			
			//orthographic projection
			for each ( zs in zsVec) {
				_sprite.addChild(zs);
				zs.superX = zs.x - cameraX;
				zs.superY = zs.y - cameraY;
			}
		}
		
		/**
		 * The zoom factor of the <code>ZContainer</code>.
		 * <p>A value of 1 corresponds to 100% of the original scale, 2 to 200%, 3 to 300%, etc.</p>
		 */
		public function get zoom():Number { return super.scaleX; }
		public function set zoom(value:Number):void { super.scaleX = super.scaleY = value }
		
		/** @private */
		public override function set scaleX(value:Number):void { throw new IllegalOperationError("This property is not available. Use the zoom property instead."); }
		/** @private */
		public override function set scaleY(value:Number):void { throw new IllegalOperationError("This property is not available. Use the zoom property instead."); }
		
		private static function zSorter(zs1:ZSprite, zs2:ZSprite):Number {
			if (zs1.z > zs2.z) return -1;
			return 1;
		}
		
	}
}
	